{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true,
    "deletable": true,
    "editable": true
   },
   "source": [
    "# Importing your own neural net"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true,
    "deletable": true,
    "editable": true
   },
   "source": [
    "Verifying the example neural net was all well and good, but you probably want to verify your own neural net now. In this tutorial, we show you how to import the parameters for a feed-forward neural net with an architecture of your choice."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "using MIPVerify\n",
    "using Gurobi\n",
    "using MAT"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We'll download a `.mat` file containing the parameters of a sample neural net containing three layers (exported from `tensorflow`). "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current\n",
      "                                 Dload  Upload   Total   Spent    Left  Speed\n",
      "100   157  100   157    0     0    495      0 --:--:-- --:--:-- --:--:--   495\n",
      "100  239k  100  239k    0     0   354k      0 --:--:-- --:--:-- --:--:--  354k\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "Dict{String,Any} with 26 entries:\n",
       "  \"fc1/weight\"           => Float32[0.000222324 6.78186f-6 … 0.00111799 -0.0005…\n",
       "  \"fc3/weight/Adam_1\"    => Float32[4.4054f-5 7.04086f-5 … 2.05744f-5 1.5445f-5…\n",
       "  \"logits/bias/Adam_1\"   => Float32[9.47849f-6 1.14659f-5 … 2.12202f-5 2.5675f-…\n",
       "  \"fc1/bias\"             => Float32[0.675125 -0.372304 … 0.540256 -0.468334]\n",
       "  \"fc3/bias\"             => Float32[0.239015 1.05777 … 1.87256 1.10636]\n",
       "  \"logits/weight/Adam_1\" => Float32[9.73416f-5 7.37292f-5 … 0.000153108 0.00013…\n",
       "  \"fc2/weight/Adam_1\"    => Float32[0.000456424 0.000191762 … 9.0507f-5 5.83681…\n",
       "  \"fc2/bias\"             => Float32[1.89861 1.58582 … -0.54874 1.00736]\n",
       "  \"fc3/bias/Adam_1\"      => Float32[3.96812f-6 6.44411f-6 … 4.03203f-6 1.96784f…\n",
       "  \"logits/bias/Adam\"     => Float32[-0.00108494 0.000629807 … 0.000172997 0.001…\n",
       "  \"beta1_power\"          => 0.0\n",
       "  \"fc2/bias/Adam\"        => Float32[0.00093958 0.000148308 … -0.000481088 -0.00…\n",
       "  \"logits/bias\"          => Float32[-0.167159 0.670988 … -0.163606 0.0620176]\n",
       "  \"fc1/weight/Adam\"      => Float32[2.61229f-9 2.50404f-8 … 9.15141f-9 -1.26372…\n",
       "  \"fc3/weight/Adam\"      => Float32[0.000514265 0.00114447 … 6.09192f-5 0.00064…\n",
       "  \"fc2/weight\"           => Float32[-0.243556 -0.232867 … 0.23907 -0.82437; 0.1…\n",
       "  \"fc3/bias/Adam\"        => Float32[0.000412472 7.06436f-5 … -2.81073f-5 0.0001…\n",
       "  \"logits/weight\"        => Float32[0.106404 0.587075 … 0.310872 0.152626; 0.02…\n",
       "  \"fc1/weight/Adam_1\"    => Float32[5.76757f-14 2.20114f-14 … 2.44297f-14 4.312…\n",
       "  \"fc2/weight/Adam\"      => Float32[0.0121755 0.00166487 … -0.00550308 -0.00305…\n",
       "  \"beta2_power\"          => 0.0\n",
       "  \"fc3/weight\"           => Float32[0.18034 -0.119679 … -0.0579543 0.0819598; -…\n",
       "  \"fc1/bias/Adam\"        => Float32[-9.24088f-6 0.000377022 … -0.000173221 -0.0…\n",
       "  \"fc1/bias/Adam_1\"      => Float32[2.21867f-5 9.82018f-6 … 1.34117f-5 2.2821f-…\n",
       "  \"logits/weight/Adam\"   => Float32[-0.00350437 0.000921413 … 0.000551378 0.002…\n",
       "  ⋮                      => ⋮"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "param_dict = Base.download(\"https://github.com/vtjeng/MIPVerify_data/raw/master/weights/mnist/n2.mat\") |> matread"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Layer 1"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "deletable": true,
    "editable": true
   },
   "source": [
    "Let's begin by importing the parameters for the first fully connected layer, which has 784 inputs (corresponding to a flattened 28x28 image) and 24 outputs."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Basic Approach\n",
    "\n",
    "We begin with a basic approach where we extract the weights and the biases of the fully connected layer seperately."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "784×24 Array{Float32,2}:\n",
       "  0.000222324   6.78186f-6    0.000211635  …   0.00111799   -0.00055723 \n",
       "  0.000270549  -0.000155775   0.000345145      0.0011448    -0.000638638\n",
       "  0.00157308    0.00294467    0.00133911      -0.00275523    0.00436651 \n",
       "  0.00124757   -4.55079f-5    0.000364477      0.000844778   0.00030634 \n",
       "  0.000181523   7.54999f-5    0.000184328      0.000718589  -0.000354625\n",
       "  0.00100992   -0.00056507    0.000376461  …   0.00108511   -2.14134f-5 \n",
       "  0.0011267     0.00277671    0.00199021      -0.00308363    0.0037927  \n",
       "  0.00172865    0.00107658    0.000331343     -0.000169128   0.00168143 \n",
       " -0.00152659   -0.0020568     0.000401156      0.00151572   -0.000661888\n",
       "  0.000884574  -0.000527718   0.000382487      0.00106229   -4.86445f-5 \n",
       "  0.00147657    0.00275898    0.00151834   …  -0.00248229    0.00379858 \n",
       " -3.01078f-5    0.000446275   0.000935425     -0.00300283    0.00124241 \n",
       "  2.6066f-5     0.000116717   3.48966f-6      -0.000166195   0.000101932\n",
       "  ⋮                                        ⋱                            \n",
       " -0.0616022    -0.00453137    0.0187155       -0.0498921    -0.0245561  \n",
       "  0.0195578     0.00215033   -0.0438484       -0.0184708    -0.0380633  \n",
       "  0.0591205     0.0328315    -0.0641472       -0.0294263    -0.0802063  \n",
       "  0.10835       0.00218356    0.00843114   …  -0.0408395    -0.0159919  \n",
       "  0.0664918    -0.0229892     0.0143863        0.0794955     0.0215239  \n",
       " -0.00811535    0.0067625     0.00549217       0.0318993     0.0041776  \n",
       " -0.0132527    -0.110216      0.0162308       -0.00244502    0.0169465  \n",
       "  5.43133f-5   -3.70874f-5    9.81464f-5       0.000183623   0.000160505\n",
       "  0.00131112    6.32279f-5    0.000345392  …   0.000886682   0.000304327\n",
       "  5.60821f-6    0.000752103   0.00101124      -0.00320989    0.0014562  \n",
       "  0.000598289   0.00211266    0.00166339      -0.00337056    0.00261424 \n",
       " -0.00185165   -0.00284901    0.00128107       0.00113743   -0.00045202 "
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "fc1_weight = param_dict[\"fc1/weight\"]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "1×24 Array{Float32,2}:\n",
       " 0.675125  -0.372304  -0.202615  …  -0.0190356  0.540256  -0.468334"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "fc1_bias = param_dict[\"fc1/bias\"]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We group the weights and biases in a `MatrixMultiplicationParameters`.\n",
    "\n",
    "_(NB: We have to flatten the bias layer using `squeeze` since `MatrixMultiplicationParameters` expects a 1-D array for the bias.)_"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "MIPVerify.MatrixMultiplicationParameters{Float32,Float32}(Float32[0.000222324 6.78186f-6 … 0.00111799 -0.00055723; 0.000270549 -0.000155775 … 0.0011448 -0.000638638; … ; 0.000598289 0.00211266 … -0.00337056 0.00261424; -0.00185165 -0.00284901 … 0.00113743 -0.00045202], Float32[0.675125, -0.372304, -0.202615, 0.0262003, 1.22431, -0.0849658, 0.99381, -0.198835, -0.669204, 0.896797  …  -0.25358, 0.302006, 0.141458, 0.348455, 0.493663, 0.0866294, 0.0502461, -0.0190356, 0.540256, -0.468334])"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "m1_manual = MatrixMultiplicationParameters(fc1_weight, squeeze(fc1_bias, 1))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "That was a lot to remember. Wouldn't it be nice if there was a helper function to take care of all that?\n",
    "\n",
    "### With Helper Functions"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "MIPVerify.MatrixMultiplicationParameters{Float32,Float32}(Float32[0.000222324 6.78186f-6 … 0.00111799 -0.00055723; 0.000270549 -0.000155775 … 0.0011448 -0.000638638; … ; 0.000598289 0.00211266 … -0.00337056 0.00261424; -0.00185165 -0.00284901 … 0.00113743 -0.00045202], Float32[0.675125, -0.372304, -0.202615, 0.0262003, 1.22431, -0.0849658, 0.99381, -0.198835, -0.669204, 0.896797  …  -0.25358, 0.302006, 0.141458, 0.348455, 0.493663, 0.0866294, 0.0502461, -0.0190356, 0.540256, -0.468334])"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "m1_helper = get_matrix_params(param_dict, \"fc1\", (784, 24))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`get_matrix_params` requires that 1) you specify the expected size of the layer, and 2) your weight and bias arrays following the naming convention outlined in the [documentation](https://vtjeng.github.io/MIPVerify.jl/stable/utils/import_weights.html#MIPVerify.get_matrix_params-Tuple{Dict{String,V} where V,String,Tuple{Int64,Int64}})."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As a sanity check, you can verify that the parameters we get from both methods are equal."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "true"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "m1_manual == m1_helper"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Wrapping as FullyConnectedLayer\n",
    "\n",
    "Finally, we pass the `MatrixMultiplicationParameters` to a fully connected layer."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "fully connected layer with 784 inputs and 24 output units, and a ReLU activation function."
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "fc1params = FullyConnectedLayerParameters(m1_helper)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### And now, everything summarized as a one liner"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "fully connected layer with 784 inputs and 24 output units, and a ReLU activation function."
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "fc1params = get_matrix_params(param_dict, \"fc1\", (784, 24)) |> FullyConnectedLayerParameters"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Importing the rest of the layers\n",
    "\n",
    "Since we followed the naming convention required by `get_matrix_params` when exporting our neural net parameters as a `.mat` file, importing the rest of the neural net is relatively straightforward."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "fully connected layer with 24 inputs and 24 output units, and a ReLU activation function."
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "fc2params = get_matrix_params(param_dict, \"fc2\", (24, 24)) |> FullyConnectedLayerParameters"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "fully connected layer with 24 inputs and 24 output units, and a ReLU activation function."
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "fc3params = get_matrix_params(param_dict, \"fc3\", (24, 24)) |> FullyConnectedLayerParameters"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "For the softmax layer, we import the matrix multiplication parameters in the same way, but must pass the output to a `SoftmaxParameters` instead."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "softmax layer with 24 inputs and 10 output units."
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "softmaxparams = get_matrix_params(param_dict, \"logits\", (24, 10)) |> SoftmaxParameters"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Composing the network\n",
    "\n",
    "We now put the entire network together. Since the neural net we imported fits in the basic feed-forward architecture of the [`StandardNeuralNetParameters`](https://vtjeng.github.io/MIPVerify.jl/stable/net_components/nets.html#MIPVerify.StandardNeuralNetParameters), composing the network is simple."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "convolutional neural net MNIST.n2\n",
       "  `convlayer_params` [0]:\n",
       "    (none)\n",
       "  `fclayer_params` [3]:\n",
       "    fully connected layer with 784 inputs and 24 output units, and a ReLU activation function.\n",
       "    fully connected layer with 24 inputs and 24 output units, and a ReLU activation function.\n",
       "    fully connected layer with 24 inputs and 24 output units, and a ReLU activation function.\n",
       "  `softmax_params`:\n",
       "    softmax layer with 24 inputs and 10 output units."
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "nnparams = StandardNeuralNetParameters(\n",
    "    ConvolutionLayerParameters[],\n",
    "    [fc1params, fc2params, fc3params],\n",
    "    softmaxparams,\n",
    "    \"MNIST.n2\"\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Verifying that you imported the network correctly\n",
    "It's important to make sure that you imported the network correctly. We do this by passing in images from the test set."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "0.9706"
      ]
     },
     "execution_count": 18,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "mnist = read_datasets(\"MNIST\")\n",
    "MIPVerify.frac_correct(nnparams, mnist.test, 10000)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Finding and Adversarial Example\n",
    "\n",
    "Finally, we find an adversarial example for a sample input."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "sample_image = MIPVerify.get_image(mnist.train.images, 1);"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\u001b[36m[notice | MIPVerify]: Loading model from cache.\n",
      "\u001b[39m\u001b[36m[notice | MIPVerify]: Attempting to find adversarial example. Neural net predicted label is 8, target labels are [10]\n",
      "\u001b[39mOptimize a model with 3433 rows, 3280 columns and 46856 nonzeros\n",
      "Variable types: 3208 continuous, 72 integer (72 binary)\n",
      "Coefficient statistics:\n",
      "  Matrix range     [2e-07, 6e+02]\n",
      "  Objective range  [1e+00, 1e+00]\n",
      "  Bounds range     [1e+00, 2e+02]\n",
      "  RHS range        [2e-02, 6e+02]\n",
      "Presolve removed 2872 rows and 2184 columns\n",
      "Presolve time: 0.16s\n",
      "Presolved: 561 rows, 1096 columns, 41184 nonzeros\n",
      "\n",
      "MIP start did not produce a new incumbent solution\n",
      "MIP start violates constraint R1072 by 2.000000000\n",
      "\n",
      "Variable types: 1024 continuous, 72 integer (72 binary)\n",
      "\n",
      "Root relaxation: objective 0.000000e+00, 329 iterations, 0.03 seconds\n",
      "\n",
      "    Nodes    |    Current Node    |     Objective Bounds      |     Work\n",
      " Expl Unexpl |  Obj  Depth IntInf | Incumbent    BestBd   Gap | It/Node Time\n",
      "\n",
      "     0     0    0.00000    0   10          -    0.00000      -     -    0s\n",
      "Another try with MIP start\n",
      "     0     0    0.00000    0   13          -    0.00000      -     -    1s\n",
      "     0     0    0.00000    0   13          -    0.00000      -     -    1s\n",
      "     0     0    0.00000    0   12          -    0.00000      -     -    1s\n",
      "     0     0    0.00000    0   12          -    0.00000      -     -    1s\n",
      "     0     0    0.00000    0    8          -    0.00000      -     -    1s\n",
      "     0     0    0.00000    0    8          -    0.00000      -     -    1s\n",
      "     0     0    0.00000    0    3          -    0.00000      -     -    2s\n",
      "     0     0    0.00000    0    3          -    0.00000      -     -    2s\n",
      "H    0     0                      57.7757038    0.00000   100%     -    3s\n",
      "     0     2    0.00000    0    3   57.77570    0.00000   100%     -    3s\n",
      "H   73    76                      54.6638363    0.00000   100%  97.8    4s\n",
      "H  208    95                       6.8360245    0.00000   100%  71.9    4s\n",
      "   334   111    6.81479   13    7    6.83602    0.00000   100%  68.1    5s\n",
      "H  898   333                       6.5908231    0.00000   100%  61.0    6s\n",
      "  1070   426    0.00000   13    9    6.59082    0.00000   100%  75.1   10s\n",
      "H 2077   454                       6.0339914    0.00000   100%  78.7   14s\n",
      "  2311   550     cutoff   38         6.03399    0.00000   100%  77.7   15s\n",
      "  3975   992     cutoff   43         6.03399    0.00000   100%  83.8   20s\n",
      "  5400  1130     cutoff   43         6.03399    0.00000   100%  92.1   25s\n",
      "  7967  1750    0.82455   42    6    6.03399    0.82455  86.3%  83.4   30s\n",
      " 10642  2115    1.69176   31    1    6.03399    1.69176  72.0%  77.9   35s\n",
      " 13476  2731     cutoff   48         6.03399    1.99495  66.9%  75.4   40s\n",
      " 16238  3297     cutoff   53         6.03399    2.23675  62.9%  72.8   45s\n",
      "H18518  3297                       5.7996980    2.39718  58.7%  71.0   51s\n",
      "\n",
      "Cutting planes:\n",
      "  Projected Implied bound: 2\n",
      "  Flow cover: 7\n",
      "\n",
      "Explored 20848 nodes (1417232 simplex iterations) in 60.04 seconds\n",
      "Thread count was 8 (of 8 available processors)\n",
      "\n",
      "Solution count 6: 5.7997 6.03399 6.59082 ... 57.7757\n",
      "Pool objective bound 2.70214\n",
      "\n",
      "Time limit reached\n",
      "Best objective 5.799697996205e+00, best bound 2.702140324064e+00, gap 53.4089%\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\u001b[1m\u001b[33mWARNING: \u001b[39m\u001b[22m\u001b[33mNot solved to optimality, status: UserLimit\u001b[39m\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "Dict{Symbol,Any} with 7 entries:\n",
       "  :PerturbationParameters => additive\n",
       "  :TargetIndexes          => [10]\n",
       "  :SolveStatus            => :UserLimit\n",
       "  :Output                 => JuMP.GenericAffExpr{Float64,JuMP.Variable}[0.10640…\n",
       "  :Model                  => Minimization problem with:…\n",
       "  :Perturbation           => JuMP.Variable[__anon__ __anon__ __anon__ __anon__ …\n",
       "  :PerturbedInput         => JuMP.Variable[__anon__ __anon__ __anon__ __anon__ …"
      ]
     },
     "execution_count": 16,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "MIPVerify.find_adversarial_example(nnparams, sample_image, 10, GurobiSolver(TimeLimit=60), \n",
    "    model_build_solver = GurobiSolver(TimeLimit=1, OutputFlag=0))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "There we go! Now it's your turn to try to verify your own neural network."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Julia 0.6.0",
   "language": "julia",
   "name": "julia-0.6"
  },
  "language_info": {
   "file_extension": ".jl",
   "mimetype": "application/julia",
   "name": "julia",
   "version": "0.6.0"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
